#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string.h>
#include <iphlpapi.h>

#include "Log.h"
#include "MessageTypes.h"
#include "CryptManager.h"
#include "FOnline.h"

using namespace std;

static const DWORD FOMSG_HASH_TABLE[10] = { FOMSG0_HASH, FOMSG1_HASH, FOMSG2_HASH, FOMSG3_HASH, FOMSG4_HASH, FOMSG5_HASH, FOMSG6_HASH, FOMSG7_HASH, FOMSG8_HASH, FOMSG9_HASH };

static const DWORD FOPROTO_HASH_TABLE[14] = { FOPROTO0_HASH, FOPROTO1_HASH, FOPROTO2_HASH, FOPROTO3_HASH, FOPROTO4_HASH, FOPROTO5_HASH, FOPROTO6_HASH, FOPROTO7_HASH, FOPROTO8_HASH, FOPROTO9_HASH,
		FOPROTO10_HASH, FOPROTO11_HASH, FOPROTO12_HASH, FOPROTO13_HASH };

static const char* TEXT_MSG_FILE_NAME[] = { "FOTEXT.MSG", "FODLG.MSG", "FOOBJ.MSG", "FOGAME.MSG", "FOGM.MSG", "FOCOMBAT.MSG", "FOQUEST.MSG", "FOHOLO.MSG", "FOCRAFT.MSG", "FOINTERNAL.MSG", };

int cpu_id = 0;

void CPUID(void) {
	asm (
			"pusha\n\t"
			"xor %eax,%eax\n\t"
			"inc %eax\n\t"
			"cpuid\n\t"
			"mov %eax, _cpu_id\n\t"
			"popa"
	);
}

void *zlib_alloc(void *opaque, unsigned int items, unsigned int size) {
	return calloc(items, size);
}

void zlib_free(void *opaque, void *address) {
	free(address);
}

bool FOnline::FillSockAddr(const char *name, u_short hostshort) {
	sockdata.sin_family = AF_INET;
	sockdata.sin_port = htons(hostshort);
	sockdata.sin_addr.s_addr = inet_addr(name);

	if (sockdata.sin_addr.s_addr == INADDR_NONE) {
		struct hostent *remoteHost;

		remoteHost = gethostbyname(name);
		if (!remoteHost) {
			LOG_MSG("Can't resolve remote host " << name << " error " << WSAGetLastError() << ".");
			return false;
		}
		sockdata.sin_addr.s_addr = **(u_int**) remoteHost->h_addr_list;
	}

	return true;
}

bool FOnline::InitNet(void) {
	WSADATA wsadata;

	LOG_MSG("[FOnline::InitNet] Network init...");
	NetInited = false;

	if (WSAStartup(winsockver, &wsadata)) {
		LOG_MSG("[FOnline::InitNet] WSAStartup error " << WSAGetLastError() << ".");
		return false;
	}

	if (!FillSockAddr(REMOTE_ADDRESS, REMOTE_PORT)) {
		return false;
	};

	if (!NetConnect()) {
		return false;
	}

	NetInited = true;
	LOG_MSG("[FOnline::InitNet] Network init successful.");
	return true;
}

bool FOnline::NetConnect(void) {
	char optval[4];

	LOG_MSG("[FOnline::NetConnect] Connecting to server.");

	// set encryption keys for the net buffers
	in_buff->SetEncryptKey(0L);
	out_buff->SetEncryptKey(0L);
	// create socket
	sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

	if (sock == INVALID_SOCKET) {
		LOG_MSG("[FOnline::NetConnect] Create socket error " << WSAGetLastError() << ".");
		return false;
	}

	// disable nagle
	*optval = 1;
	if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, optval, 4)) {
		LOG_MSG( "[FOnline::NetConnect] Can't set TCP_NODELAY (disable Nagle) to socket, error" << WSAGetLastError() << ".");
		return false;
	}

	// connect to host
	if (connect(sock, (SOCKADDR *) &sockdata, sizeof(sockdata))) {
		LOG_MSG("[FOnline::NetConnect] Can't connect to game server, error " << WSAGetLastError() << ".");
		return false;
	}

	LOG_MSG("[FOnline::NetConnect] Connecting successful.");

	ZlibInited = false;
	// zlib inflate init
	zstrm.zalloc = zlib_alloc;
	zstrm.zfree = zlib_free;
	zstrm.opaque = NULL;

	if (inflateInit(&zstrm) != Z_OK) {
		LOG_MSG("[FOnline::NetConnect] ZStream InflateInit error.");
		return false;
	}

	ZlibInited = true;
	return true;
}

FOnline::FOnline() :
		sock(INVALID_SOCKET), winsockver(WINSOCK_VER), in_buff(NULL), out_buff(NULL), UID0_KEY(NULL), UID1_KEY(NULL), UID2_KEY(NULL), UID3_KEY(NULL), UID4_KEY(NULL), crypto(NULL), UIDXOR(
				0xF145668AUL), UIDOR(0UL), UIDCALC(0x45012345UL), NetInited(false), ZlibInited(false), DisableZlibCompression(false) {
	in_buff = new (nothrow) BufferManager();
	out_buff = new (nothrow) BufferManager();
	crypto = new (nothrow) CryptManager();
	sock_buff.buf = new (nothrow) char[4096];
	if (sock_buff.buf) {
		sock_buff.len = 4096UL;
	}
	else {
		sock_buff.len = 0UL;
	}
}

FOnline::~FOnline() {
	SAFEDEL(in_buff);
	SAFEDEL(out_buff);
	SAFEDEL(sock_buff.buf);
}

int FOnline::NetInput(bool compress) {
	timeval timeout;
	fd_set writefds, exceptfds;
	DWORD recvbytes, recvbytes_sum, flags;
	WSABUF databuf;

	// only poll sockets' state - select will return immediately without blocking
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;

	// clear socket descriptor lists
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	// add current socket to descriptor lists
	FD_SET(sock, &writefds);
	FD_SET(sock, &exceptfds);

	// query status of sockets
	if (select(0, &writefds, NULL, &exceptfds, &timeout) == SOCKET_ERROR) {
		LOG_MSG("[FOnline::NetInput] Select error " << WSAGetLastError() << ".");
		return (0);
	}

	// check for errors
	if (FD_ISSET(sock,&exceptfds)) {
		LOG_MSG("[FOnline::NetInput] Socket error " << WSAGetLastError() << ".");
	}

	// check if socket is ready for transmission
	if (!FD_ISSET(sock,&writefds)) {
		// socket is not yet ready for transmission
		return (0);
	}

	databuf.buf = sock_buff.buf;
	databuf.len = sock_buff.len;
	flags = 0;

	if (WSARecv(sock, &databuf, 1u, &recvbytes, &flags, NULL, NULL) != SOCKET_ERROR) {
		if (recvbytes) {
			recvbytes_sum = recvbytes;
			if (recvbytes != databuf.len) {
				FOCL_NETINPUT_DECOMPRESS: in_buff->Refresh();
				if (!compress || DisableZlibCompression) {
					// store received uncompressed data
					in_buff->Push(sock_buff.buf, recvbytes_sum, !ENCRYPT);
				}
				else {
					// received data needs decompression
					zstrm.next_in = (Bytef*) sock_buff.buf;
					zstrm.avail_in = recvbytes_sum;
					zstrm.next_out = (Bytef*) (in_buff->GetEndPos() + in_buff->GetData());
					zstrm.avail_out = (in_buff->GetLen() - in_buff->GetEndPos());
					if (inflate(&zstrm, Z_SYNC_FLUSH) != Z_OK) {
						LOG_MSG("[FOnline::NetInput] ZStream Inflate error.");
						return (-3);
					}

					// update end position with decompressed data
					in_buff->SetEndPos((DWORD) &zstrm.next_out[-(DWORD) in_buff->GetData()]);

					if (zstrm.avail_in != 0UL) {
						for (;;) {
							in_buff->GrowBuf(2048);
							zstrm.next_out = (Bytef *) (in_buff->GetEndPos() + in_buff->GetData());
							zstrm.avail_out = (in_buff->GetLen() - in_buff->GetEndPos());
							if (inflate(&zstrm, Z_SYNC_FLUSH) != Z_OK) {
								break;
							}

							// update end position with decompressed data
							in_buff->SetEndPos((DWORD) &zstrm.next_out[-(DWORD) in_buff->GetData()]);

							if (zstrm.avail_in == 0UL) {
								// TODO Return number of bytes received
								return (1);
							}
						}
						LOG_MSG("[FOnline::NetInput] ZStream Inflate continue error.");
						return (-4);
					}
				}
				// TODO Return number of bytes received
				return (1);
			}
			// receive buffer is too small to hold all data
			for (;;) {
				WSABUF new_buff;

				// create new buffer with double the size as before - shall throw in case of failure
				new_buff.buf = new char[sock_buff.len * 2UL];
				new_buff.len = sock_buff.len * 2UL;
				// copy contents of old buffer to new one
				memcpy(new_buff.buf, sock_buff.buf, sock_buff.len);
				// safe delete old buffer
				SAFEDEL(sock_buff.buf);
				// update buffer info
				sock_buff.buf = new_buff.buf;
				sock_buff.len = new_buff.len;

				databuf.buf = sock_buff.buf + recvbytes_sum;
				databuf.len = sock_buff.len - recvbytes_sum;
				flags = 0;

				if (WSARecv(sock, &databuf, 1u, &recvbytes, &flags, NULL, NULL) == SOCKET_ERROR) {
					LOG_MSG( "[FOnline::NetInput] Socket error (2) while receive from server, error<" << WSAGetLastError() << ">.");
					return (-1);
				}
				if (!recvbytes) {
					break;
				}
				recvbytes_sum += recvbytes;
				if (recvbytes_sum != databuf.len) {
					goto FOCL_NETINPUT_DECOMPRESS;
				}
			}
			LOG_MSG("[FOnline::NetInput] Socket is closed (2).");
			return (-2);
		}
		else {
			LOG_MSG("[FOnline::NetInput] Socket is closed.");
		}
		return (-2);
	}
	LOG_MSG("[FOnline::NetInput] Socket error while receive from server, error<" << WSAGetLastError() << ">.");
	return (-1);
}

bool FOnline::NetOutput(void) {
	timeval timeout;
	fd_set writefds, exceptfds;
	WSABUF databuf;
	DWORD bytessent, bytessent_sum;

	if (out_buff->GetReadPos() >= out_buff->GetEndPos()) {
		return (true);
	}

	// only poll sockets' state - select will return immediately without blocking
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;

	// clear socket descriptor lists
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	// add current socket to descriptor lists
	FD_SET(sock, &writefds);
	FD_SET(sock, &exceptfds);

	// query status of sockets
	if (select(0, NULL, &writefds, &exceptfds, &timeout) == SOCKET_ERROR) {
		LOG_MSG("Select error " << WSAGetLastError() << ".");
		return (false);
	}

	// check for errors
	if (FD_ISSET(sock,&exceptfds)) {
		LOG_MSG("Socket error " << WSAGetLastError() << ".");
		return (false);
	}

	// check if socket is ready for reception
	if (!FD_ISSET(sock,&writefds)) {
		// socket is not yet ready for reception
		return (true);
	}

	// output buffer is empty
	if (out_buff->GetEndPos() <= 0UL) {
		out_buff->Reset();
		return (true);
	}
	// send data from output buffer
	bytessent_sum = 0UL;
	for (;;) {
		databuf.buf = (out_buff->GetData() + bytessent_sum);
		databuf.len = (out_buff->GetEndPos() - bytessent_sum);

		if (WSASend(sock, &databuf, 1uL, &bytessent, 0, NULL, NULL) == SOCKET_ERROR) {
			LOG_MSG("[FOnline::NetOutput] Socket error while send to server, error <" << WSAGetLastError() << ">.");
			NetInited = false;
			return (false);
		}
		if (!bytessent) {
			LOG_MSG("[FOnline::NetOutput] Socket error while send to server, error <" << WSAGetLastError() << ">.");
			NetInited = false;
			return (false);
		}
		bytessent_sum += bytessent;
		if (bytessent_sum >= out_buff->GetEndPos()) {
			out_buff->Reset();
			return (true);
		}
	}
	LOG_MSG("[FOnline::NetOutput] Socket error while send to server, error <" << WSAGetLastError() << ">.");
	NetInited = false;
	return (false);
}

void FOnline::NetDisconnect(void) {
	LOG_MSG("Disconnect.");

	if (ZlibInited) {
		inflateEnd(&zstrm);
	}
	ZlibInited = false;

	if (sock != (SOCKET) SOCKET_ERROR) {
		closesocket(sock);
	}
	sock = (SOCKET) SOCKET_ERROR;
	NetInited = false;

	in_buff->Reset();
	out_buff->Reset();
	in_buff->SetEncryptKey(0UL);
	out_buff->SetEncryptKey(0UL);

	LOG_MSG("Disconnect success.");
}

void FOnline::ParseSocket(void) {
	if (sock != (SOCKET) SOCKET_ERROR) {
		if (NetInput(COMPRESS) >= 0) {
			NetProcess();
			// TODO Ping
			NetOutput();
		}
		else {
			NetInited = false;
		}
		if (!NetInited) {
			NetDisconnect();
		}
	}
}

void FOnline::NetProcess(void) {
	if (!in_buff->NeedProcess()) {
		return;
	}
	for (;;) {
		DWORD msgid;

		// get message id
		*in_buff >> msgid;

		LOG_MSG("Received message<" << TODEC((WORD)msgid >> 8U) << ">.");

		switch ((DWORD) msgid) {
		case NET_ONCRITTERACTION:
			Net_OnCritterAction();
			break;
		case NET_ONCHECKUID2:
			Net_OnCritterAction();
			break;
		case NET_ONCONTAINERINFO:
			Net_OnContainerInfo();
			break;
		case NET_ONSOMEITEM:
			Net_OnSomeItem();
			break;
		case NET_ONANIMATEITEM:
			Net_OnAnimateItem();
			break;
		case NET_ONADDITEMONMAP:
			Net_OnAddItemOnMap();
			break;
		case NET_ONCHANGEITEMONMAP:
			Net_OnAddItemOnMap();
			break;
		case NET_ONERASEITEMFROMMAP:
			Net_OnEraseItemFromMap();
			break;
		case NET_ONCHOSENERASEITEM:
			Net_OnChosenEraseItem();
			break;
		case NET_ONCRAFTRESULT:
			Net_OnCraftResult();
			break;
		case NET_ONCHOSENCLEARITEMS:
			Net_OnChosenClearItems();
			break;
		case NET_ONCHOSENADDITEM:
			Net_OnChosenAddItem();
			break;
		case NET_ONCRAFTASK:
			Net_OnCraftAsk();
			break;
		case NET_ONCRITTERPARAM:
			Net_OnCritterParam();
			break;
		case NET_ONCHOSENPARAM:
			Net_OnChosenParam();
			break;
		case NET_ONCRITTERXY:
			Net_OnCritterXY();
			break;
		case NET_ONCHOSENPARAMS:
			Net_OnChosenParams();
			break;
		case NET_ONCRITTERMOVE:
			Net_OnCritterMove();
			break;
		case NET_ONMAPTEXTMSG:
			Net_OnMapTextMsg();
			break;
		case NET_ONMAPTEXTMSGLEX:
			Net_OnMapTextMsgLex();
			break;
		case NET_ONCRITTERDIR:
			Net_OnCritterDir();
			break;
		case NET_ONMAPTEXT:
			Net_OnMapText();
			break;
		case NET_ONTEXTMSG_1:
			Net_OnTextMsg(true);
			break;
		case NET_ONTEXTMSG_0:
			Net_OnTextMsg(false);
			break;
		case NET_ONCHECKUID1:
			Net_OnCheckUID1();
			break;
		case NET_ONTEXT:
			Net_OnText();
			break;
		case NET_ONITEMLEXEMS:
			Net_OnItemLexems();
			break;
		case NET_ONADDCRITTER_2:
			Net_OnAddCritter(true);
			break;
		case NET_ONREMOVECRITTER:
			Net_OnRemoveCritter();
			break;
		case NET_ONCRITTERLEXEMS:
			Net_OnCritterLexems();
			break;
		case NET_ONADDCRITTER_1:
			Net_OnAddCritter(false);
			break;
		case NET_ONCHECKUID0:
			Net_OnCheckUID0();
			break;
		case NET_ONPING:
			Net_OnPing();
			break;
		case NET_ONLOGINSUCCESS:
			Net_OnLoginSuccess();
			break;
		case NET_ONREGSUCCESS:
			Net_OnRegSuccess();
			break;
		case NET_ONSHOWSCREEN:
			Net_OnShowScreen();
			break;
		case NET_ONGAMEINFO:
			Net_OnGameInfo();
			break;
		case NET_ONLOADMAP:
			Net_OnLoadMap();
			break;
		case NET_ONMAP:
			Net_OnMap();
			break;
		case NET_ONPLAYERSBARTERSETHIDE:
			Net_OnPlayersBarterSetHide();
			break;
		case NET_ONPLAYSOUND_1:
			Net_OnPlaySound(true);
			break;
		case NET_ONCHOSENTALK:
			Net_OnChosenTalk();
			break;
		case NET_ONPLAYERSBARTER:
			Net_OnPlayersBarter();
			break;
		case NET_ONPLAYSOUND_0:
			Net_OnPlaySound(false);
			break;
		case NET_ONCOMBATRESULT:
			Net_OnCombatResult();
			break;
		case NET_ONEFFECT:
			Net_OnEffect();
			break;
		case NET_ONFLYEFFECT:
			Net_OnFlyEffect();
			break;
		case NET_ONCRITTERSETANIMS:
			Net_OnCritterSetAnims();
			break;
		case NET_ONCRITTERANIMATE:
			Net_OnCritterAnimate();
			break;
		case NET_ONCRITTERITEMDATA:
			Net_OnCritterItemData();
			break;
		case NET_ONCRITTERKNOCKOUT:
			Net_OnCritterKnockout();
			break;
		case NET_ONCRITTERMOVEITEM:
			Net_OnCritterMoveItem();
			break;
		case NET_ONPROTOITEMDATA:
			Net_OnProtoItemData();
			break;
		case NET_ONGLOBALENTRANCES:
			Net_OnGlobalEntrances();
			break;
		case NET_ONCHECKUID3:
			Net_OnCheckUID3();
			break;
		case NET_ONMSGDATA:
			Net_OnMsgData();
			break;
		case NET_ONFOLLOW:
			Net_OnFollow();
			break;
		case NET_ONGLOBALINFO:
			Net_OnGlobalInfo();
			break;
		case NET_ONVIEWMAP:
			Net_OnViewMap();
			break;
		case NET_ONRUNCLIENTSCRIPT:
			Net_OnRunClientScript();
			break;
		case NET_ONDROPTIMERS:
			Net_OnDropTimers();
			break;
		case NET_ONUSERHOLOSTR:
			Net_OnUserHoloStr();
			break;
		case NET_ONQUEST_0:
			Net_OnQuest(false);
			break;
		case NET_ONQUEST_1:
			Net_OnQuest(true);
			break;
		case NET_ONHOLOINFO:
			Net_OnHoloInfo();
			break;
		case NET_ONSCORES:
			Net_OnScores();
			break;
		case NET_ONAUTOMAPSINFO:
			Net_OnAutomapsInfo();
			break;
		case NET_ONCHECKUID4:
			Net_OnCheckUID4();
			break;
		default: {
			DWORD temp;

			LOG_MSG("Invalid msg<" << TODEC((WORD)msgid >> 8U) << ">. Seek valid.");
			// skip next DWORD and seek valid message
			(*in_buff) >> temp;
			in_buff->SeekValidMsg();
			break;
		}
		}

		// test whether further processing is needed
		if ((NetInited) && (in_buff->NeedProcess())) {
			continue;
		}

		// finished processing
		return;
	}
}

void FOnline::Dump(void) {
	out_buff->Dump();
}

void FOnline::Net_SendLogIn(const char* name, const char* pass) {
	char login_string[31];
	char password_string[31];
	BYTE digest[32];

	// prepare [login] and [password]
	ZeroMemory(login_string,31);
	ZeroMemory(password_string,31);
	strcpy((char*) login_string, name);
	strcpy((char*) password_string, pass);

	// push NET_SENDLOGIN command
	*out_buff << NET_SENDLOGIN;
	// push NET Protocol Version ID
	*out_buff << NET_PROTOCOL_VERSION;
	// push UID0_KEY
	*out_buff << (unsigned int) (*UID0_KEY);
	// set default encryption keys for the BufferManager instances
	out_buff->SetEncryptKey(*UID0_KEY + 12345);
	in_buff->SetEncryptKey(*UID0_KEY + 12345);
	// push LOGIN_NAME
	out_buff->Push(login_string, 30UL, false);
	// push UID1_KEY
	*out_buff << (int) *UID1_KEY;
	// push LOGIN_PASS HASH code
	crypto->ClientPassHash(login_string, password_string, digest);
	out_buff->Push((char*) digest, 32UL, false);
	// push language code
	*out_buff << LANGUAGE_CODE;
	// push FOMSG HASH values (10 objects)
	for (int i = 0; i < 10; i++) {
		*out_buff << (int) FOMSG_HASH_TABLE[i];
	}
	// push UIDXOR
	*out_buff << (int) UIDXOR;
	// push UID2_KEY
	*out_buff << (int) *UID2_KEY;
	// push UID3_KEY
	*out_buff << (int) *UID3_KEY;
	// push UIDOR
	*out_buff << (int) UIDOR;
	// push PROTOS HASH values (14 objects)
	for (int i = 0; i < 14; i++) {
		*out_buff << (int) FOPROTO_HASH_TABLE[i];
	}
	// push UIDCALC
	*out_buff << (int) UIDCALC;
	// push DEFAULT_COMBAT_MODE setting
	*out_buff << DEFAULT_COMBAT_MODE;
	// push UID4_KEY
	*out_buff << (int) *UID4_KEY;

	LOG_MSG("[FOnline::Net_SendLogIn] Finished.");
}

void FOnline::Net_SendCommand(char* cmd) {
	// push NET_SENDCOMMAND command
	*out_buff << NET_SENDCOMMAND;
	// TODO Implement function
}

void FOnline::Net_SendLoadMapOk(void) {
	// push NET_SENDLOADMAPOK command
	*out_buff << NET_SENDLOADMAPOK;
}

void FOnline::Net_SendGetGameInfo(void) {
	// push NET_SENDGETGAMEINFO command
	*out_buff << NET_SENDGETGAMEINFO;
}

void FOnline::Net_SendGetScores(void) {
	// push NET_SENDGETSCORES command
	*out_buff << NET_SENDGETSCORES;
}

void FOnline::Net_SendDir(BYTE direction) {
	// push NET_SENDDIR command
	*out_buff << NET_SENDDIR;
	*out_buff << direction;
}

void FOnline::Net_SendGiveGlobalInfo(BYTE unknown) {
	// push NET_SENDGIVEGLOBALINFO command
	*out_buff << NET_SENDGIVEGLOBALINFO;
	*out_buff << unknown;
}

void FOnline::Net_SendPing(BYTE unknown) {
	// push NET_SENDGIVEGLOBALINFO command
	*out_buff << NET_SENDPING;
	*out_buff << unknown;
}

void FOnline::Net_SendKarmaVoting(DWORD critterId, bool vote) {
	// push NET_SENDKARMAVOTING command
	*out_buff << NET_SENDKARMAVOTING;
	*out_buff << critterId;
	*out_buff << vote;
}

void FOnline::Net_SendMove(WORD pos_x, WORD pos_y, bool mode) {
	DWORD unknown;

	// push NET_SENDMOVE command
	if (mode) {
		*out_buff << NET_SENDMOVE_RUN;
	}
	else {
		*out_buff << NET_SENDMOVE_WALK;
	}

	unknown = 0UL;

	*out_buff << unknown;
	*out_buff << pos_x;
	*out_buff << pos_y;
}

void FOnline::Net_SendText(char* text) {
	DWORD packet_size;
	WORD length;
	BYTE mode;

	length = strlen(text);
	packet_size = length + 11UL;

	// push NET_SENDTEXT command
	*out_buff << NET_SENDTEXT;
	*out_buff << packet_size;
	*out_buff << mode;
	*out_buff << length;
	out_buff->Push(text, length, ENCRYPT);
}

void FOnline::Net_SendTalk(void) {
	DWORD var2;
	BYTE var1, var3;

	// push NET_SENDTALK command
	*out_buff << NET_SENDTALK; // (DWORD)
	*out_buff << var1; // (BYTE)
	*out_buff << var2; // (DWORD)
	*out_buff << var3; // (BYTE)
}

// TODO Find out proper meaning of variables
void FOnline::Net_SendGiveMap(bool bool1, WORD unsignedShortInt, DWORD unsignedLongInt, DWORD unsignedLongInt1, DWORD unsignedLongInt2, DWORD unsignedLongInt3) {
	// push NET_SENDGIVEMAP command
	*out_buff << NET_SENDGIVEMAP; // (DWORD)
	*out_buff << bool1; // (BOOL)
	*out_buff << unsignedShortInt; // (WORD)
	*out_buff << unsignedLongInt; // (DWORD)
	*out_buff << unsignedLongInt1; // (DWORD)
	*out_buff << unsignedLongInt2; // (DWORD)
	*out_buff << unsignedLongInt3; // (DWORD)
}

/** The function generates the unique encryption keys.
 *
 * @note The server forbids more than two zero UIDs.
 *
 */
bool FOnline::Init(void) {
	// Generate UID0_KEY
	{
		DWORD VolumeSerialNumber;

		VolumeSerialNumber = 0UL;

		UID0_KEY = new (nothrow) DWORD();

		if (UID0_KEY) {
			*UID0_KEY = 0UL;
		}
		else {
			return (false);
		}

		// The path is also hard-coded in an obfuscated way into the client executable
		if (GetVolumeInformation("c:\\", NULL, 0, &VolumeSerialNumber, NULL, NULL, NULL, 0)) {
			VolumeSerialNumber ^= 0x12345678UL;
		}
		else {
			VolumeSerialNumber = 0UL;
		}
		*UID0_KEY = VolumeSerialNumber;
		*UID0_KEY = (*UID0_KEY | 0x4800) ^ 0x4000;

		LOG_MSG("UID0_KEY: " << TOHEX(DWORD,*UID0_KEY));

		// zero keys are forbidden
		if ((!UID0_KEY) || ((DWORD) *UID0_KEY) == 0x00UL) {
			return (false);
		}
	}
	// Generate UID1_KEY
	{
		ULONG ulOutBufLen;
		IP_ADAPTER_INFO* pAdapterInfo;

		UID1_KEY = new (nothrow) DWORD();

		if (UID1_KEY) {
			*UID1_KEY = 0UL;
		}
		else {
			return (false);
		}

		ulOutBufLen = sizeof(IP_ADAPTER_INFO);
		pAdapterInfo = new (nothrow) (IP_ADAPTER_INFO);

		if (!pAdapterInfo) {
			return (false);
		}

		if (ERROR_BUFFER_OVERFLOW == GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) {
			SAFEDEL(pAdapterInfo);
			pAdapterInfo = (IP_ADAPTER_INFO*) new (nothrow) char[ulOutBufLen];
		}

		if (NO_ERROR != GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) {
			*UID1_KEY = 0UL;
		}
		else {
			int offset;
			int temp1, temp2, temp3;

			temp3 = 0;
			offset = 3;
			do {
				// a network adapter looks like this: {DEE2963C-0539-4BC1-8AB5-E1DB688FD721}
				temp1 = ((pAdapterInfo->AdapterName[offset - 1] % 2 != 0) << (offset - 1)) | ((pAdapterInfo->AdapterName[offset - 2] % 2 != 0) << (offset - 2))
						| ((pAdapterInfo->AdapterName[offset - 3] % 2 != 0) << (offset - 3));
				temp2 = (pAdapterInfo->AdapterName[offset] % 2 != 0) << offset;
				offset += 4;
				temp3 |= temp2 | temp1;
			}
			while (offset - 3 < 32);
			*UID1_KEY = temp3;
		}

		*UID1_KEY |= 0x4000000u;
		*UID1_KEY = (*UID1_KEY | 0x200000) ^ 0x200000;

		LOG_MSG("UID1_KEY: " << TOHEX(DWORD,*UID1_KEY));

		// zero keys are forbidden
		if ((!UID1_KEY) || ((DWORD) *UID1_KEY) == 0x00UL) {
			return (false);
		}
	}
	// Generate UID2_KEY
	{
		UID2_KEY = new (nothrow) DWORD();

		if (UID2_KEY) {
			*UID2_KEY = 0UL;
		}
		else {
			return (false);
		}

		CPUID();
		*UID2_KEY = cpu_id;

		*UID2_KEY |= 0x80000000u;
		*UID2_KEY = (*UID2_KEY | 0x40000000) ^ 0x40000000;

		LOG_MSG("UID2_KEY: " << TOHEX(DWORD,*UID2_KEY));

		// zero keys are forbidden
		if ((!UID2_KEY) || ((DWORD) *UID2_KEY) == 0x00UL) {
			return (false);
		}
	}
	// Generate UID3_KEY
	{
		HW_PROFILE_INFO HwProfileInfo;

		UID3_KEY = new (nothrow) DWORD();

		if (UID3_KEY) {
			*UID3_KEY = 0UL;
		}
		else {
			return (false);
		}

		if (GetCurrentHwProfileA(&HwProfileInfo)) {
			*UID3_KEY = *(DWORD *) &HwProfileInfo.szHwProfileGuid[0];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[4];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[8];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[12];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[16];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[20];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[24];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[28];
			*UID3_KEY ^= *(DWORD *) &HwProfileInfo.szHwProfileGuid[32];
		}
		else {
			*UID3_KEY = 0UL;
		}
		*UID3_KEY |= 0x20UL;
		*UID3_KEY = (*UID3_KEY | 0x800) ^ 0x800;

		LOG_MSG("UID3_KEY: " << TOHEX(DWORD,*UID3_KEY));

		// zero keys are forbidden
		if ((!UID3_KEY) || ((DWORD) *UID3_KEY) == 0x00UL) {
			return (false);
		}
	}
	// Generate UID4_KEY
	{
		SYSTEM_INFO SystemInfo;
		int index;

		UID4_KEY = new (nothrow) DWORD();

		if (UID4_KEY) {
			*UID4_KEY = 0UL;
		}
		else {
			return (false);
		}

		GetSystemInfo(&SystemInfo);

		*UID4_KEY = (unsigned __int16) SystemInfo.dwOemId;

		*UID4_KEY ^= SystemInfo.dwNumberOfProcessors << 8;
		*UID4_KEY *= SystemInfo.dwProcessorType << 10;
		*UID4_KEY ^= SystemInfo.wProcessorLevel << 12;
		*UID4_KEY += SystemInfo.wProcessorRevision << 16;

		index = 0;
		do {
			*UID4_KEY |= (IsProcessorFeaturePresent(index) != 0) << index;
			++index;
		}
		while (index < 32);

		*UID4_KEY |= 0x800000;
		*UID4_KEY = (*UID4_KEY | 0x400) ^ 0x400;

		LOG_MSG("UID4_KEY: " << TOHEX(DWORD,*UID4_KEY));

		// zero keys are forbidden
		if ((!UID4_KEY) || ((DWORD) *UID4_KEY) == 0x00UL) {
			return (false);
		}
	}
	// Generate UIDXOR
	{
		UIDXOR ^= *UID4_KEY;
		UIDXOR ^= *UID1_KEY;
		UIDXOR ^= *UID3_KEY;
		UIDXOR ^= *UID2_KEY;
		UIDXOR ^= *UID0_KEY;

		LOG_MSG("UIDXOR: " << TOHEX(DWORD,UIDXOR));
	}
	// Generate UIDOR
	{
		UIDOR |= *UID4_KEY;
		UIDOR |= *UID1_KEY;
		UIDOR |= *UID3_KEY;
		UIDOR |= *UID2_KEY;
		UIDOR |= *UID0_KEY;

		LOG_MSG("UIDOR: " << TOHEX(DWORD,UIDOR));
	}
	// Generate UIDCALC
	{
		UIDCALC += *UID4_KEY;
		UIDCALC += *UID1_KEY;
		UIDCALC += *UID3_KEY;
		UIDCALC += *UID2_KEY;
		UIDCALC += *UID0_KEY;

		LOG_MSG("UIDCALC: " << TOHEX(DWORD,UIDCALC));
	}

	return (true);
}

void FOnline::Net_OnCritterAction(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterAction]");
}

void FOnline::Net_OnCheckUID2(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCheckUID2]");
}

void FOnline::Net_OnContainerInfo(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnContainerInfo]");
}

void FOnline::Net_OnSomeItem(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnSomeItem]");
}

void FOnline::Net_OnAnimateItem(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnAnimateItem]");
}

/** On add item on map
 *
 * Message data size: 135 bytes
 */
void FOnline::Net_OnAddItemOnMap(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnAddItemOnMap]");
}

/** On add item on map
 *
 * Message data size: 128 bytes
 */
void FOnline::Net_OnChangeItemOnMap(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnChangeItemOnMap]");
}

void FOnline::Net_OnEraseItemFromMap(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnEraseItemFromMap]");
}

void FOnline::Net_OnChosenEraseItem(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnChosenEraseItem]");
}

void FOnline::Net_OnCraftResult(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCraftResult]");
}

/** On chosen clear items
 *
 * Message data size: 4 bytes
 */
void FOnline::Net_OnChosenClearItems(void) {
	// TODO Implement feature
	LOG_MSG("[FOnline::Net_OnChosenClearItems]");
}

/** On chosen clear items
 *
 * Message data size: 131 bytes
 */
void FOnline::Net_OnChosenAddItem(void) {
	DWORD itemId;
	WORD protoId;
	BYTE var1;
	BYTE data[120];

	*in_buff >> itemId; // (DWORD)
	*in_buff >> protoId; // (WORD)
	*in_buff >> var1; // (BYTE)

	// TODO Process message
	in_buff->Pop((char*) data, 120UL);
	LOG_MSG("[FOnline::Net_OnChosenAddItem]");
}

void FOnline::Net_OnCraftAsk(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCraftAsk]");
}

void FOnline::Net_OnCritterParam(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterParam]");
}

/** On load map event handler
 *
 * Message data size: 10 bytes
 */
void FOnline::Net_OnChosenParam(void) {
	WORD index;
	int value;

	*in_buff >> index; // (WORD)
	*in_buff >> value; // (int)

	LOG_MSG("[FOnline::Net_OnChosenParam] Index<" << TODEC(index) << "> Value<" << TODEC(value) << ">.");
}

void FOnline::Net_OnCritterXY(void) {
	DebugBreak();
	LOG_MSG("Net_OnCritterXY]");
}

void FOnline::Net_OnChosenParams(void) {
	BYTE chosen_params[4000];

	in_buff->Pop((char*) chosen_params, 4000);

	LOG_MSG("[FOnline::Net_OnChosenParams] Skipped processing.");
}

void FOnline::Net_OnCritterMove(void) {
	DebugBreak();
	LOG_MSG("Net_OnCritterMove]");
}

void FOnline::Net_OnMapTextMsg(void) {
	DebugBreak();
	LOG_MSG("Net_OnMapTextMsg]");
}

void FOnline::Net_OnMapTextMsgLex(void) {
	DebugBreak();
	LOG_MSG("Net_OnMapTextMsgLex]");
}

void FOnline::Net_OnCritterDir(void) {
	DebugBreak();
	LOG_MSG("Net_OnCritterDir]");
}

void FOnline::Net_OnMapText(void) {
	DebugBreak();
	LOG_MSG("Net_OnMapText]");
}

void FOnline::Net_OnTextMsg(bool mode) {
	DebugBreak();
	LOG_MSG("Net_OnTextMsg]");
}

void FOnline::Net_OnCheckUID1(void) {
	DebugBreak();
	LOG_MSG("Net_OnCheckUID1]");
}

void FOnline::Net_OnText(void) {
	DWORD packet_size, var1;
	WORD var3, length;
	BYTE var2;
	bool var4;

	*in_buff >> packet_size; // (DWORD) full packet size
	*in_buff >> var1; // (DWORD)
	*in_buff >> var2; // (BYTE)
	*in_buff >> var3; // (WORD)
	*in_buff >> var4; // (BOOL)
	*in_buff >> length; // (WORD)

	{
		char buffer[length];

		in_buff->Pop((char*)buffer,length);
		LOG_MSG("Net_OnText] " << (char*)buffer);
	}
}

void FOnline::Net_OnItemLexems(void) {
	DebugBreak();
	LOG_MSG("Net_OnItemLexems]");
}

void FOnline::Net_OnAddCritter(bool mode) {
	DWORD packet_size, CritterClId, var2, var7, var8, var9, var10, var11, var12, var13, var16;
	WORD var3, var4, var15, var17, var18;
	short var14;
	BYTE var5, var6;
	BYTE data1[30];
	int data2[1000];

	*in_buff >> packet_size; // (DWORD) full packet size
	*in_buff >> CritterClId; // (DWORD)
	*in_buff >> var2; // (DWORD)
	*in_buff >> var3; // (WORD)
	*in_buff >> var4; // (WORD)
	*in_buff >> var5; // (BYTE)
	*in_buff >> var6; // (BYTE)
	*in_buff >> var7; // (DWORD)
	*in_buff >> var8; // (DWORD)
	*in_buff >> var9; // (DWORD)
	*in_buff >> var10; // (DWORD)
	*in_buff >> var11; // (DWORD)
	*in_buff >> var12; // (DWORD)
	*in_buff >> var13; // (DWORD)
	*in_buff >> var14; // (short)

	if (mode) {
		*in_buff >> var15; // (WORD)
		*in_buff >> var16; // (DWORD)
	}
	else {
		in_buff->Pop((char*) data1, 30);
	}

	memset(data2, 0, 4000);

	*in_buff >> var17; // (WORD)

	if (var17 > 1000U) {
		var17 = 1000U;
	}

	if (var17 > 0) {
		WORD index;

		index = var17;

		do {
			*in_buff >> var18; // (WORD)
			if (var18 >= 1000U) {
				var18 = 0U;
			}
			*in_buff >> data2[var18]; // (int)
			index--;
		}
		while (index);
	}

	if (in_buff->IsError()) {
		LOG_MSG("[FOnline::Net_OnAddCritter] Wrong MSG data.");
	}
	else {
		// TODO Process contents
	}
	LOG_MSG("[FOnline::Net_OnAddCritter]");
}

void FOnline::Net_OnRemoveCritter(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnRemoveCritter]");
}

void FOnline::Net_OnCritterLexems(void) {
	DWORD packet_size, critter_id;
	WORD lexems_length;
	BYTE lexems_data[128];

	*in_buff >> packet_size; // (DWORD) full packet size
	*in_buff >> critter_id; // (DWORD)
	*in_buff >> lexems_length; // (WORD)

	if ((lexems_length + 1U) < 128U) {
		if (lexems_length) {
			in_buff->Pop((char*) lexems_data, lexems_length);
		}
		lexems_data[lexems_length] = (BYTE) 0U;
		if (in_buff->IsError()) {
			LOG_MSG("[FOnline::Net_OnCritterLexems] Wrong MSG data.");
		}
		else {
			// TODO Implement process
		}
	}
	else {
		LOG_MSG("[FOnline::Net_OnCritterLexems]  Invalid lexems length<" << TODEC(lexems_length) << ">, disconnect.");
		NetInited = false;
	}
	LOG_MSG("[FOnline::Net_OnCritterLexems]");
}

void FOnline::Net_OnCheckUID0(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCheckUID0]");
}

/** On ping event handler
 *
 * Message data size: 5 bytes
 */
void FOnline::Net_OnPing(void) {
	BYTE var1;

	*in_buff >> var1; // (BYTE)

	if (var1 == (BYTE) 1U) {
		// ???
	}
	else if (var1 == (BYTE) 2U) {
		// optionally send back ping
		*out_buff << NET_ONPING; // (DWORD)
		*out_buff << var1; // (BYTE)
		LOG_MSG("[FOnline::Net_OnPing] Scheduled response.");
	}
	else if (var1 == (BYTE) 0U) {
		// ???
	}

	LOG_MSG("[FOnline::Net_OnPing]");
}

/** On load map event handler
 *
 * Message data size: 12 bytes
 */
void FOnline::Net_OnLoginSuccess(void) {
	DWORD in_key, out_key;

	LOG_MSG("[FOnline::Net_OnLoginSuccess] Authentication success.");

	*in_buff >> out_key; // output buffer's new encryption key
	*in_buff >> in_key; // input buffer's new encryption key

	// set received encryption keys for the net buffers
	in_buff->SetEncryptKey(in_key);
	out_buff->SetEncryptKey(out_key);
}

void FOnline::Net_OnRegSuccess(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnRegSuccess]");
}

void FOnline::Net_OnShowScreen(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnShowScreen]");
}

/** On load map event handler
 *
 * Message data size: 55 bytes
 */
void FOnline::Net_OnGameInfo(void) {
	WORD YearStart, Year, Month, Day, Hour, Minute, Second, TimeMultiplier;
	int weather_1;
	BYTE weather_2;
	bool unkwnown_1, unkwnown_2;
	BYTE *MapDayTime;
	BYTE *MapDayColor;

	MapDayTime = new BYTE[16];
	MapDayColor = new BYTE[12];

	*in_buff >> YearStart; // (WORD)
	*in_buff >> Year; // (WORD)
	*in_buff >> Month; // (WORD)
	*in_buff >> Day; // (WORD)
	*in_buff >> Hour; // (WORD)
	*in_buff >> Minute; // (WORD)
	*in_buff >> Second; // (WORD)
	*in_buff >> TimeMultiplier; // (WORD)

	*in_buff >> weather_1; // (int)
	*in_buff >> weather_2; // (BYTE)

	*in_buff >> unkwnown_1; // (BOOL)
	*in_buff >> unkwnown_2; // (BOOL)

	in_buff->Pop((char*) MapDayTime, 16);
	in_buff->Pop((char*) MapDayColor, 12);

	if (in_buff->IsError()) {
		LOG_MSG("[FOnline::Net_OnGameInfo] Wrong MSG data.");
	}
	else {
		LOG_MSG("[FOnline::Net_OnGameInfo] Date: " << TODEC(Year) << "." << TODEC(Month) << "." << TODEC(Day) << " " << TODEC(Hour) << ":" << TODEC(Minute));
	}

	SAFEDEL(MapDayTime);
	SAFEDEL(MapDayColor);
	LOG_MSG("[FOnline::Net_OnGameInfo]");
}

/** On load map event handler
 *
 * Message data size: 23 bytes
 */
void FOnline::Net_OnLoadMap(void) {
	DWORD map_tiles_hash, map_walls_hash, map_scenery_hash;
	WORD map_pid;
	int map_weather_1;
	BYTE map_weather_2;

	LOG_MSG("[FOnline::Net_OnLoadMap] Change map...");

	*in_buff >> map_pid; // (WORD)
	*in_buff >> map_weather_1; // (int)
	*in_buff >> map_weather_2; // (BYTE)
	*in_buff >> map_tiles_hash; // (DWORD)
	*in_buff >> map_walls_hash; // (DWORD)
	*in_buff >> map_scenery_hash; // (DWORD)

	LOG_MSG(
			"[FOnline::Net_OnLoadMap] Hashes of map<" << TOHEX(WORD,map_pid) << ">: tiles<" << TOHEX(DWORD,map_tiles_hash) << ">, walls<" << TOHEX(DWORD,map_walls_hash) << ">, scenery<" << TOHEX(DWORD,map_scenery_hash) << ">.\n");

	// TODO Implement binary format MAP handling
	// TODO Request up to date MAP files
	//Net_SendGiveMap();

	if (map_pid) {
		// local map needs to be loaded (pid != 0)
		LOG_MSG("[FOnline::Net_OnLoadMap] Local map loaded.");
		Net_SendLoadMapOk();
	}
	else {
		// global map needs to be loaded (pid == 0)
		LOG_MSG("[FOnline::Net_OnLoadMap] Global map loaded.");
		Net_SendLoadMapOk();
	}
	LOG_MSG("[FOnline::Net_OnLoadMap]");
}

void FOnline::Net_OnMap(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnMap]");
}

void FOnline::Net_OnPlayersBarterSetHide(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnPlayersBarterSetHide]");
}

void FOnline::Net_OnPlaySound(bool mode) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnPlaySound]");
}

void FOnline::Net_OnChosenTalk(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnChosenTalk]");
}

void FOnline::Net_OnPlayersBarter(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnPlayersBarter]");
}

void FOnline::Net_OnCombatResult(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCombatResult]");
}

void FOnline::Net_OnEffect(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnEffect]");
}

void FOnline::Net_OnFlyEffect(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnFlyEffect]");
}

void FOnline::Net_OnCritterSetAnims(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterSetAnims]");
}

void FOnline::Net_OnCritterAnimate(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterAnimate]");
}

void FOnline::Net_OnCritterItemData(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterItemData]");
}

void FOnline::Net_OnCritterKnockout(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterKnockout]");
}

void FOnline::Net_OnCritterMoveItem(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCritterMoveItem]");
}

void FOnline::Net_OnProtoItemData(void) {
	DWORD packet_size, hash, size;
	BYTE proto_type;
	BYTE* buffer;

	*in_buff >> packet_size; // (DWORD) full packet size
	*in_buff >> proto_type; // (BYTE) proto type identifier
	*in_buff >> hash; // (DWORD) hash

	// create buffer for proto data (header is excluded)
	size = packet_size - sizeof(DWORD) * 3UL - sizeof(BYTE);
	buffer = new BYTE[size];

	// copy data to local buffer
	in_buff->Pop((char*) buffer, size);

	// test for buffer errors
	if (in_buff->IsError()) {
		LOG_MSG("[FOnline::Net_OnProtoItemData] Wrong MSG data.");
	}
	else {
		/* test hash of received data */
		if (crypto->Crc32(buffer, size) != (hash)) {
			LOG_MSG("[FOnline::Net_OnProtoItemData]  Hash error.");
		}
		else {
			DWORD file_index;

			// TODO Find out the real file name or at least file index
			// get proto_type index from buffer
			if (size >= 8UL) {
				file_index = ((DWORD*) buffer)[1];
			}
			else {
				file_index = 0UL;
			}

			char file_name[] = "proto_00.pro\0";

			file_name[6] = '0' + (char) (file_index / 10UL);
			file_name[7] = '0' + (char) (file_index % 10UL);
			file_index++;

			// save raw proto file
			FILE* handle = fopen(file_name, "wb");
			fwrite(buffer, 1, size, handle);
			fclose(handle);
			LOG_MSG("[FOnline::Net_OnProtoItemData] Saved proto_" << file_name << ". Hash<" << TOHEX(DWORD,hash) << ">.");
		}
	}
	SAFEDEL(buffer);
}

void FOnline::Net_OnGlobalEntrances(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnGlobalEntrances]");
}

void FOnline::Net_OnCheckUID3(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCheckUID3]");
}

void FOnline::Net_OnMsgData(void) {
	DWORD packet_size, lang_id, hash, size;
	WORD msg_num;
	BYTE* buffer;

	*in_buff >> packet_size; // full packet size
	*in_buff >> lang_id; // language pack identifier
	*in_buff >> msg_num; // message number
	*in_buff >> hash; // hash

	// create buffer for message data (header is excluded)
	size = packet_size - sizeof(DWORD) * 4UL - sizeof(WORD);
	buffer = new BYTE[size];

	// copy data to local buffer
	in_buff->Pop((char*) buffer, size);

	// test for buffer errors
	if (in_buff->IsError()) {
		LOG_MSG("[FOnline::Net_OnMsgData] Wrong MSG data.");
		SAFEDEL(buffer);
		return;
	}

	// TODO Test language pack identifier and update config files in case of differences

	//
	if (msg_num < 10U) {
		/* test hash of received data */
		if (crypto->Crc32(buffer, size) != (hash)) {
			LOG_MSG("[FOnline::Net_OnMsgData] Invalid hash<" << TEXT_MSG_FILE_NAME[msg_num] << ">.");
			SAFEDEL(buffer);
			return;
		}
		// Save raw message
		{
			FILE* handle = fopen(TEXT_MSG_FILE_NAME[msg_num], "wb");
			fwrite(buffer, 1, size, handle);
			fclose(handle);
			LOG_MSG( "[FOnline::Net_OnMsgData] Saved " << TEXT_MSG_FILE_NAME[msg_num] << ". Hash<" << TOHEX(DWORD,hash) << ">.");
			return;
		}
	}

	LOG_MSG("[FOnline::Net_OnMsgData] Incorrect value of msg num.");
	SAFEDEL(buffer);
	return;
}

void FOnline::Net_OnFollow(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnFollow]");
}

void FOnline::Net_OnGlobalInfo(void) {
	DWORD packet_size;
	BYTE fields;

	*in_buff >> packet_size; // (DWORD)
	*in_buff >> fields; // (BYTE)

	if (ISBITSET(BIT0,fields)) {
		DWORD var1, var6;
		WORD count, var2, var3, var4, var5;

		*in_buff >> count; // (WORD)

		if (count > 0U) {
			WORD index;

			index = 0U;
			do {
				*in_buff >> var1; // (DWORD)
				*in_buff >> var2; // (WORD)
				*in_buff >> var3; // (WORD)
				*in_buff >> var4; // (WORD)
				*in_buff >> var5; // (WORD)
				*in_buff >> var6; // (DWORD)
				index++;
			}
			while (index < count);
		}
	}
	if (ISBITSET(BIT5,fields)) {
		bool var1;
		DWORD var2, var7;
		WORD var3, var4, var5, var6;

		*in_buff >> var1; // (BOOL)
		*in_buff >> var2; // (DWORD)
		*in_buff >> var3; // (WORD)
		*in_buff >> var4; // (WORD)
		*in_buff >> var5; // (WORD)
		*in_buff >> var6; // (WORD)
		*in_buff >> var7; // (DWORD)
	}
	if (ISBITSET(BIT2,fields)) {
		bool GmapWait;
		DWORD var5, var6;
		WORD var1, var2, var3, var4;

		*in_buff >> var1; // (WORD)
		*in_buff >> var2; // (WORD)
		*in_buff >> var3; // (WORD)
		*in_buff >> var4; // (WORD)
		*in_buff >> var5; // (DWORD)
		*in_buff >> GmapWait; // (BOOL)

		*in_buff >> var6; // (DWORD)
	}
	if (ISBITSET(BIT3,fields)) {
		char var[2500];
		in_buff->Pop(var, 2500UL);
	}
	if (ISBITSET(BIT4,fields)) {
		WORD var1, var2;
		BYTE var3;

		*in_buff >> var1; // (WORD)
		*in_buff >> var2; // (WORD)
		*in_buff >> var3; // (BYTE)
	}
	if (ISBITSET(BIT4,fields)) {

	}
	if (in_buff->IsError()) {
		LOG_MSG("[FOnline::Net_OnGlobalInfo] Wrong MSG data.");
	}
	LOG_MSG("[FOnline::Net_OnGlobalInfo]");
}

void FOnline::Net_OnViewMap(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnViewMap]");
}

void FOnline::Net_OnRunClientScript(void) {
	// TODO Implement feature

	DWORD packet_size;

	*in_buff >> packet_size; // (DWORD)

	// @warning Hack to quickly get rid of the data from the buffer
	{
		DWORD size = packet_size - (sizeof(DWORD) * 2);
		BYTE data[size];
		in_buff->Pop((char*) data, size);
	}
	LOG_MSG("[FOnline::Net_OnRunClientScript]");
}

/** On drop timers handler
 *
 * Message data size: 4 bytes
 */
void FOnline::Net_OnDropTimers(void) {
	// TODO Implement timers
	LOG_MSG("[FOnline::Net_OnDropTimers]");
}

void FOnline::Net_OnUserHoloStr(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnUserHoloStr]");
}

void FOnline::Net_OnQuest(bool mode) {
	// TODO Implement feature

	DWORD packet_size;

	*in_buff >> packet_size; // (DWORD)

	// @warning Hack to quickly get rid of the data from the buffer
	{
		DWORD size = packet_size - (sizeof(DWORD) * 2);
		BYTE data[size];
		in_buff->Pop((char*) data, size);
	}
	LOG_MSG("[FOnline::Net_OnQuest]");
}

void FOnline::Net_OnHoloInfo(void) {
	DWORD packet_size;
	bool var1;
	WORD var2, var3;
	BYTE data[1000];

	*in_buff >> packet_size; // (DWORD)
	*in_buff >> var1; // (BOOL)
	*in_buff >> var2; // (WORD)
	*in_buff >> var3; // (WORD)

	if (var1) {
		memset(data, 0, 1000u);
	}
	if (var3) {
		if ((var3 * 4) > 1000) {
			DebugBreak(); // ERROR
		}
		in_buff->Pop((char*) data, (var3 * 4));
	}
	LOG_MSG("[FOnline::Net_OnHoloInfo]");
}

void FOnline::Net_OnScores(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnScores]");
}

void FOnline::Net_OnAutomapsInfo(void) {
	// TODO Implement feature

	DWORD packet_size;

	*in_buff >> packet_size; // (DWORD)

	// @warning Hack to quickly get rid of the data from the buffer
	{
		DWORD size = packet_size - (sizeof(DWORD) * 2);
		BYTE data[size];
		in_buff->Pop((char*) data, size);
	}
	LOG_MSG("[FOnline::Net_OnAutomapsInfo]");
}

void FOnline::Net_OnCheckUID4(void) {
	DebugBreak();
	LOG_MSG("[FOnline::Net_OnCheckUID4]");
}

bool FOnline::MainLoop(void) {
	if (Init()) {
		if (InitNet()) {
			Net_SendLogIn(LOGIN_NAME, LOGIN_PASS);
			Dump();
		}
	}

	while (NetInited) {
		ParseSocket();
		Sleep(100UL);
	}
	return (false);
}

int main() {
	FOnline* fo = new (nothrow) FOnline();

	fo->MainLoop();
	LOG_MSG("Finished.");

	return 0;
}
